
#include "ASprite.h"
#include "AXML.h"
#include "ASpriteModel.h"
//#include "ASpriteAction.h"
#include "ACache.h"
#include "AWorld.h"
#include "AStage.h"
#include "AStageLayer.h"
#include "AMath2d.h"

int CASprite::counter = 0;

CASprite::CASprite(CAStageLayer* player, const char* model)
{
    CCGLProgram *alphaTestShader = CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColorAlphaTest);
    
	GLint alphaValueLocation = glGetUniformLocation(alphaTestShader->getProgram(), kCCUniformAlphaTestValue);
    alphaTestShader->setUniformLocationWith1f(alphaValueLocation, 0.0f);
    
    this->setShaderProgram(alphaTestShader);

	_Assert(player);
	_Assert(model);

	_type = ST_Frames;
	//_psprParent = null;

	counter++;

	_actionBoundingBox.origin = CCPointZero;
	_actionBoundingBox.size = CAWorld::sharedWorld().getScreenSize();

	_modname = model;
	_groupname = model;

	//_Trace("sprite mod=%s created", model);

	_setInnerState(SLS_INIT);

	_Assert(player);
	_pLayer = player;

	//base class has set the autorelease flag
	//autorelease();

	_model = (CASpriteModel*)CACache::sharedCache().hit(model, CACHE_MODEL);
	_Assert(null != _model);
	_model->retain();

	CCTexture2D* pTexture2D;

	pTexture2D = CCTextureCache::sharedTextureCache()->addImage(getTextureName().c_str());
	//for init(), for null-controller safing
	CCSprite::initWithTexture(pTexture2D, CCRectZero);

	setLiveArea(CCRectZero);
	setMoveDirection(0);
	setMoveSpeed(0);
	setZOrder(10);

	_bUseGlobalTimer = false;

	_currentPose = null;

	_bIsTouchable = false;
	_nControlOrder = -1;

	_speed = 0;

	//DO NOT move this line up, cause retain is overrided, it will access some member in this class
	autorelease();
}

CASprite::~CASprite(void)
{
	//_Trace("sprite %s, gname=%s released", this->getModName().c_str(), this->getGroupName().c_str());
	//CCLOGINFO("sprite gname=%s released", this->getGroupName().c_str());
	_currentPose = null;

	_model->release();
	_model = null;
	//if (null != _pSubMeshes)
	//{
	//	_pSubMeshes->release();
	//	_pSubMeshes = null;
	//}
	counter--;
}

void CASprite::retain()
{
	//_Trace(">> sprite mod=%s retain, refcount=%d", _model->file().c_str(), this->retainCount());
	//CAScheduledNode<CCSprite>::retain();
	CCSprite::retain();
}

void CASprite::release()
{
	//_Trace("<< sprite mod=%s release, refcount=%d", _model->file().c_str(), this->retainCount());
	//CAScheduledNode<CCSprite>::release();
	CCSprite::release();
}

void CASprite::setStringSettings(const map<CAString, CAString>* p)
{
	if (null == p)
		return;
	map<CAString, CAString>::const_iterator it = p->begin();
	while (it != p->end())
	{
		_settings().setString(it->first.c_str(), it->second);
		it++;
	}
}

//get texture name from _model
const CAString& CASprite::getTextureName() const
{
	return _model->tex();
}

void CASprite::_setInnerState(ESpriteLifeState ls)
{
	_inner_state = ls;
}

void CASprite::onEnter()
{
	//CAScheduledNode<CCSprite>::onEnter();
	CCSprite::onEnter();
	CASpriteContainer::onEnter();
}

void CASprite::onExit()
{
	//CAScheduledNode<CCSprite>::onExit();
	CASpriteContainer::onExit();
	CCSprite::onExit();
}


void CASprite::_onUpdateCurrentPose()
{
	CAStage* pstage = this->_pLayer->stage();
	this->updateAnimation(pstage, _pLayer->getTimeNow());
}

CCSpriteFrame* CASprite::_onAnimateUpdateFrame(float timeNow)
{
	CCSpriteFrame* pframe = _currentPose->lerpFrame(timeNow, this->displayFrame());
	if (pframe && !this->isFrameDisplayed(pframe))
	{
		this->setDisplayFrame(pframe);
	}
	return pframe;
}

void CASprite::_onAnimateUpdate(float timeNow)
{
	CAStage* pstage = this->_pLayer->stage();
	//CCSprite* pspr = this;

	//anchor pos
	CCSprite::setAnchorPoint(ccp(_combinedKey.ax, _combinedKey.ay));
	//position
	CCPoint pos = ccp(_combinedKey.x, _combinedKey.y);
	pstage->game2pixel(pos, _bFollowCamera);
#if defined(_DEBUG)
	_posOffsetUsed = pstage->offset();
	_gameKeyUsed = _gameKey;
	//_Trace("sprite[%p]:%s use offset .x=%.2f .y=%.2f", this, this->getGroupName().c_str(), _posOffsetUsed.x, _posOffsetUsed.y);
#endif
	this->_adjustPosition(pos);
	_Assert(pos.x > -999999999.0f && pos.x < 999999999.0f);
	_Assert(pos.y > -999999999.0f && pos.y < 999999999.0f);
	CCSprite::setPosition(pos);

	CCSprite::setScaleX(_combinedKey.scalex);
	CCSprite::setScaleY(_combinedKey.scaley);
	//rotation
	float rot = _combinedKey.rot;
	rot += getMoveDirection();

	CCSprite::setRotation(rot);

	CCSprite::setVertexZ(getZOrder());

	//alpha
	int alpha = (int)(_combinedKey.alpha * 255);
	if (alpha < 0) alpha = 0; else if (alpha > 255) alpha = 255;
	CCSprite::setOpacity(alpha);
}

void CASprite::onUpdate()
{
	CASpriteContainer::onUpdate();

	//update current pose
	if (SLS_RUNNING == _inner_state)
	{
		//if (!isPaused())
		{
			//double dt = CCDirector::sharedDirector()->getAnimationInterval();
			CAStage* pstage = this->_pLayer->stage();
			double dt = pstage->getTimeDelta();
			_Assert(_speed > -999999999.0f && _speed < 999999999.0f);
			_gameKey.x += (float)(_dirCos * _speed * dt); //_timeDelta;
			_gameKey.y += (float)(_dirSin * _speed * dt); //_timeDelta;
			//_Trace("timeDelta = %f", _timeDelta);
		}
		_onUpdateCurrentPose();

		if (!CCRect::CCRectEqualToRect(_rectLive, CCRectZero))
		{
			CCRect rectLive = _rectLive;
			//_pLayer->stage()->pixel2game(rectLive.origin, true);

			//get game bounding box
			CCRect rect = getViewBoundingBox();
			if (!CCRect::CCRectIntersectsRect(rect, rectLive))
			{
				this->killMyself();
			}
		}
	}
	else if (SLS_DYING == _inner_state)
	{
		_setInnerState(SLS_DIED);
		_pLayer->removeSprite(this);
		return;
	}
}

void CASprite::switchPose(const CAString& pose)
{
	if (!_model->hasPose(pose))
		return;

	bool bv = this->getIsVisible();
	this->setIsVisible(false);

	CASpriteModelPose* ppose = _model->getPose(pose);

	const CAString& mesh = ppose->getMesh();
	if (mesh.length() <= 0)
	{
		//use zero texture
		setTextureRect(CCRectZero);
	}
	else
	{
		//when action runs, mesh.c_str() will be used
	}

	_setInnerState(SLS_RUNNING);
	_currentPose = ppose;

	CAStage* pstage = this->_pLayer->stage();

	ccTime st;
	if (this->_bUseGlobalTimer)
	{
		st = 0;
	}
	else
	{
		float offset = ppose->getModifierTimeOffset();
		st = _pLayer->getTimeNow() + offset;
	}
	this->startAnimation(
		st, 
		ppose->getKeys().ptr(), ppose->getKeys().count(), 
		ppose->getMode(), ppose->getModifierDuration());

	this->setIsVisible(bv);
}

void CASprite::onBeforeStateChanging(const CAString& olds, const CAString& news)
{
}

void CASprite::onStateChanged(const CAString& olds, const CAString& news)
{
}

void CASprite::setState(const CAString& state)
{
	if (_state == state)
		return;
	CAString olds = _state;
	onBeforeStateChanging(olds, state);
	_state = state;
	onStateChanged(olds, _state);
}

void CASprite::setLiveArea(const CCRect& rect, bool bPercent)
{ 
	_rectLive = rect;
	if (bPercent)
	{
		CAWorld::percent2view(_rectLive);
	}
}

CCRect CASprite::getGameBoundingBox(bool b4Collid)
{
	CCRect rect = this->boundingBox();
	_pLayer->stage()->pixel2game(rect.origin, true);
	if (b4Collid)
	{
		_Assert(null != _currentPose);
		const CCSize& size = _currentPose->collid();
		rect.size.width *= size.width;
		rect.size.height*= size.height;
	}
	return rect;
}

CCRect CASprite::getViewBoundingBox(bool b4Collid)
{
	CCRect rect = this->boundingBox();

#if defined(_DEBUG) && 0
	CCPoint posKK = this->getPos();
	CCPoint posCC = this->getPosition();
	CCPoint pos = posKK;
	_pLayer->stage()->game2pixel(posKK, _bFollowCamera);
	_Assert(posCC.x == posKK.x && posCC.y == posKK.y);
	CCPoint posOffset = _pLayer->stage()->offset();
	CCPoint posa = this->getAnchorPoint();
	float xx = rect.origin.x + rect.size.width * posa.x;
	float yy = rect.origin.y + rect.size.height * posa.y;
	_Assert(CAUtils::almostEqual(xx, posKK.x, 0.01f) && CAUtils::almostEqual(yy, posKK.y, 0.01f));
#endif

	if (b4Collid)
	{
		_Assert(null != _currentPose);
		const CCSize& size = _currentPose->collid();
		rect.size.width *= size.width;
		rect.size.height*= size.height;
	}
	return rect;
}

bool CASprite::isCollidWith(CASprite* pspr, bool bView)
{
	if (!pspr->isRunning())
	{
		return false;
	}

	CCRect rect1;
	CCRect rect2;
	
	if (bView)
	{
		rect1 = this->getViewBoundingBox(true);
		rect2 = pspr->getViewBoundingBox(true);
	}
	else
	{
		rect1 = this->getGameBoundingBox(true);
		rect2 = pspr->getGameBoundingBox(true);
	}
	bool r = CCRect::CCRectIntersectsRect(rect1, rect2);
	if (r) return true;
	//return false;

	if (this->getUpdateCounter() < 2 && pspr->getUpdateCounter() < 2)
		return false;

	//use the move area(max rect) to test colliding first
	float dx1;
	float dy1;
	if (this->getUpdateCounter() < 2)
	{
		dx1 = dy1 = 0;
	}
	else
	{
		dx1 = this->_combinedKey.x - this->_combinedKeyLast.x;
		dy1 = this->_combinedKey.y - this->_combinedKeyLast.y;
	}
	CARect rect1From;
	rect1From = rect1;
	rect1From.x -= dx1;
	rect1From.y -= dy1;
	CARect rect1max = rect1From.unionWith(rect1);

	//same way for rect2
	float dx2, dy2;
	if (pspr->getUpdateCounter() < 2)
	{
		dx2 = dy2 = 0;
	}
	else
	{
		dx2 = pspr->getCombinedKey().x - pspr->getCombinedKeyLast().x;
		dy2 = pspr->getCombinedKey().y - pspr->getCombinedKeyLast().y;
	}
	CARect rect2From;
	rect2From = rect2;
	rect2From.x -= dx2;
	rect2From.y -= dy2;
	CARect rect2max = rect2From.unionWith(rect2);
	
	//move area test
	if (!rect1max.isIntersectWith(rect2max))
		return false;

	//may be collided

	CCRect rect2old = rect2;

	float test = 0.3f;
	int timesx, timesy, times;

#define _TEST_TIMES(dxx, wd) \
	_Abs((int)((dxx) / (0.000001f + (wd) * test)))

	//rect1
	timesx = 1 + _TEST_TIMES(dx1, rect1.size.width);
	timesy = 1 + _TEST_TIMES(dy1, rect1.size.height);
	times = timesx > timesy ? timesx : timesy;

	//rect2
	timesx = 1 + _TEST_TIMES(dx2, rect2.size.width);
	timesy = 1 + _TEST_TIMES(dy2, rect2.size.height);
	//times = timesx > timesy ? timesx : timesy;
	if (times < timesx) times = timesx;
	if (times < timesy) times = timesy;
#undef _TEST_TIMES

	CCSize delta1;
	delta1.width = dx1 / times;
	delta1.height = dy1 / times;
	CCSize delta2;
	delta2.width = dx2 / times;
	delta2.height = dy2 / times;

	int i;
	for (i = 0; i < times; i++)
	{
		rect1.origin.x -= delta1.width;
		rect1.origin.y -= delta1.height;
		rect2.origin.x -= delta2.width;
		rect2.origin.y -= delta2.height;
		bool r = CCRect::CCRectIntersectsRect(rect1, rect2);
		if (r) 
		{
			_Trace("collid in detail !!!, test %d times", times);
			return true;
		}
	}
	return false;
}

bool CASprite::_isTouched(CAEventTouch* pet)
{
	if (!this->isTouchable())
		return false;
	CCRect rect = this->getViewBoundingBox(true);
	//_Trace("sprite [%s] touch event:%.2f,%.2f, test(%.2f,%.2f,%.2f,%.2f)", this->getModName().c_str(), pet->pt().x, pet->pt().y, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
	if (CCRect::CCRectContainsPoint(rect, pet->pt()))
	{
		//_Trace("sprite:[%s] is touched", this->getModName().c_str());
		return true;
	}
	return false;
}

bool CASprite::onEvent(CAEvent* pEvent)
{
	if (!this->getIsVisible())
		return false;

	switch (pEvent->type())
	{
	case ET_Touch:
		{
			if (_bIsTouchable)
			{
				if (_isTouched((CAEventTouch*)pEvent))
				{
					onTouched((CAEventTouch*)pEvent);
					return true;
				}
				else
				{
					onTouchLeave((CAEventTouch*)pEvent);
				}
			}
		}
		break;
	}

	return false;
}
